# geometry.two_d.line -- Module for line geometry
#
# Copyright (c) 2000, V.Kelson and M.Bakker
#

from math import pi,cos

import geometry.xy
import base 
import point
import circle
import rectangle

FLOAT_TOLERANCE = 1.0e-6

class Line(base.Geometry2d):
	'''Class for Line Geometry

	Python call: Line(pt1,pt2,width), width is optional

	Attributes:
	
	  - pt1: first XY of line
	  - pt2: second XY of line
	  - width: width of line
	  - z1: complex coordinate of pt1
	  - z2: complex coordinate of pt2
	  - zc: complex coordinate of center of line
	  - zl: complex coordinate 0.5(z2-z1)
	  - halflength: half the length of the line'''
	def __init__(self,pt1,pt2,width=0.0):
		'''Constructor

		Python call: Line(pt1,pt2,width). 
		pt1 and pt2 are XY or Point objects of the ends of the line; width is optional'''
		self.pt1 = pt1
		self.pt2 = pt2
		self.width = float(width)
		self.z1 = complex(pt1)
		self.z2 = complex(pt2)
		self.zc = 0.5 * ( self.z2 + self.z1 )
		self.zl = 0.5 * ( self.z2 - self.z1 )
		self.halfLength = abs(self.zl)
	def __repr__(self):
		return 'Line(' + repr(self.pt1) + ',' + repr(self.pt2) + ')'
	def __str__(self):
		return self.__repr__()
	def isInWidth(self,pt):
		Z = self.map(pt) 
		return ( abs(Z.imag) <= 0.5 * self.width / (self.halfLength) ) & ( abs(Z.real) <= 1.0 )
	def isOnBoundary(self,pt,tol=0.0):
		tol = float(tol)
		Z = self.map(pt) 
		return ( abs(Z.imag*self.halfLength) <= tol ) and ( abs(Z.real) <= 1.0 )
	def circumCircle(self,tol=0.0):
		tol=float(tol)
		return circle.Circle(self.zc.real,self.zc.imag,self.halfLength+tol)
	def map(self,pt):
		return ( complex(pt) - self.zc ) / self.zl
	def mapback(self,Z):
		z = Z * self.zl + self.zc
		return geometry.XY(z.real,z.imag)
	def distance2(self,pt):
		Z = self.map(pt)
		if Z.real < -1.0:
			return self.pt1.distance2(pt)
		elif Z.real > 1.0:
			return self.pt2.distance2(pt)
		else:
			return ( Z.imag * self.halfLength ) ** 2
	def bbox(self,tol=0.0):
		space = geometry.XY(float(tol),float(tol))
		return rectangle.Rectangle(self.pt1-space,self.pt2+space)
	def unitNormal(self):
		return (self.pt2 - self.pt1).unitNormal()
	def intersect(self,line):
		'''Computes intersection of two lines
		
		Computes the point of intersection between 'self' and 'line'
		Returns intersection point as Point object
		If the segments do not intersect, returns None'''
		# Reference: Graphics FAQ, #8
		XA,YA = self.pt1.tuple()
		XB,YB = self.pt2.tuple()
		XC,YC = line.pt1.tuple()
		XD,YD = line.pt2.tuple()
		# Check for parallel lines
		denom = (XB-XA)*(YD-YC) - (YB-YA)*(XD-XC)
		if abs(denom) < FLOAT_TOLERANCE:
			return None
		# Compute the intersection (if any)
		R = ( (YA-YC)*(XD-XC) - (XA-XC)*(YD-YC) )/ denom
		S = ( (YA-YC)*(XB-XA) - (XA-XC)*(YB-YA) )/ denom
		#	print R,S,denom
		if (0.0 <= R <= 1.0) & (0.0 <= S <= 1.0):
			return point.Point( XA+R*(XB-XA), YA+R*(YB-YA) )
		else:
			return None
	def checkPosition(self,pt):
		'''Compute position of pt relative to line
		
		Checks the direction from the point pt to the line ln and returns the tuple
		(R,S), where R and S describe the position of 'pt' relative to 'line'

		Returns:
		
		  R <  0 -- Point lies along the reverse extension of the segment
		  0.0 <= R <= 1.0 -- Point lies along the segment
		  1.0 <  R -- Point lies along the forward extension of the segment       

		Where:

		 <0 -- if pt is to the left of ln
		  0 -- if pt is on ln (or its extension)
		 >0 -- if pt is to the right of ln

		Reference:  Graphics FAQ, #7
		'''
		Z = self.map(pt)
		R = (Z.real + 1.0) / 2.0
		S = -Z.imag * self.halfLength
		return (R,S)
	def isOnLeft(self,pt):
		'''Checks if pt is left of line

		Returns TRUE if pt is to the left of the line'''
		return self.checkPosition(pt)[1] < 0.0
	def length(self):
		'''Computes the length of the line

		Returns the length of the line as a float'''
		return 2 * self.halfLength
	def area(self):
		'''Computes area of line and width

		Returns the area of the line with width as a float'''
		return 2 * self.halfLength * self.width
	def computeControlPoints(self,ncp=1):
		'''Computes 'ncp' control-points along 'self' according to the "cosine rule"
		'''
		assert ncp>0,'Number of control points must be positive'
		dxy = self.pt2-self.pt1
		dtheta = pi/(ncp+1)
		rv = []
		for i in range(ncp):
			rv.append(self.pt1 + dxy * (1.0+cos(-pi + (i+1)*dtheta))/2.0)
		return rv

